import static java.lang.Math.abs;
import java.util.List;
import java.util.Random;
import model.ActionType;
import model.Game;
import model.Hockeyist;
import model.HockeyistState;
import model.Move;
import model.Unit;
import model.World;

/**
 * Created by Constantine on 23.09.2014.
 */
public class Attack extends HelpObject implements AbstractStrategy {

    public Attack(Hockeyist self, World world, Game game, Move move) {
        super(self, world, game, move);
    }

    private static double SWING_TICKS;

    @Override
    public void run() {
        if (self.getState() == HockeyistState.SWINGING && self.getSwingTicks() >= SWING_TICKS) {
            move.setAction(ActionType.STRIKE);
            return;
        }
        if (hasPuck(self)) {  // Если я владею шайбой

            if (self.getRemainingCooldownTicks() == 0 && beatOrNotToBeat()) {  // Бить или не бить? (c) Шекспир
                return;
            }

            if (selfY == 0) { // Разводка
                Moving.to(Point.getPoint(-0.46d, new Random().nextDouble() + Moving.getSpeed(self) / game.getHockeyistMaxSpeed()), null);
                return;
            }

            if (selfX < Vars.DEAD_ZONE) {
                deadZone();
                return;
            }

            // Уворачиваемся от ближайших соперников
            List<Unit> nearAheadUnits = getUnitsAhead(self, getOpps(self));
            if (selfX > 0.0d && !nearAheadUnits.isEmpty() && nearAheadUnits.get(0).getDistanceTo(self) < game.getStickLength() * 1.5d && abs(nearAheadUnits.get(0).getAngleTo(self)) < game.getStickSector() / 2) {
                evasion(nearAheadUnits.get(0));
                return;
            }

            Moving.moveByAttackLine(getNet(true)); // Бежим по линии атаки

            if (selfX < Vars.ADJUSTMENT_ATTACK_ANGLE_X && abs(self.getAngleTo(getNet(true))) < Vars.ADJUSTMENT_ATTACK_ANGLE) {   // Корректируем угл атаки, если близкий к нужному
                move.setTurn(self.getAngleTo(getNet(true)));
            }

        } else {

            Moving.moveByDefendLine(getNet(false)); // Бежим в оборону
            if (oppBlock(self, true)) {   // толкаем всех на своем пути
                move.setAction(ActionType.STRIKE);
            }
        }
    }

    private boolean beatOrNotToBeat() {

        for (double a = 0; a <= 1.0d; a = a + 0.2d) {
            if (shootOrNotToShoot(a)) {
                move.setTurn(self.getAngleTo(getNet(true)));
                move.setSpeedUp(a);
                if (abs(self.getAngleTo(getNet(true))) < Vars.SHOOT_ANGLE) {
                    move.setAction(ActionType.STRIKE);
                }
                return true;
            } else if (swingOrNotToSwing()) {
                move.setTurn(self.getAngleTo(getNet(true)));
                if (abs(self.getAngleTo(getNet(true))) < Vars.SHOOT_ANGLE) {
                    move.setAction(ActionType.SWING);
                    SWING_TICKS = 20;
                }
                return true;
            } else if (passToNetOrNot(a)) {
                move.setTurn(self.getAngleTo(getNet(true)));
                move.setSpeedUp(a);
                if (abs(self.getAngleTo(getNet(true))) < game.getPassSector() / 2) {
                    move.setAction(ActionType.PASS);
                    move.setPassPower(1.0d);
                    move.setPassAngle(self.getAngleTo(getNet(true)));
                }
                return true;
            }
        }
        return false;
    }

    private void evasion(Unit unit) {
        double oppAngle = Math.atan2(unit.getSpeedY(), unit.getSpeedX());
        double selfAngle = self.getAngle();
        move.setTurn(-Math.signum(selfAngle) * oppAngle - selfAngle);
        move.setSpeedUp(1.0d);
    }

    private void deadZone() {
        // Мертвая зона
        move.setAction(ActionType.SWING);
        move.setTurn(self.getAngleTo(getNet(false)));
        SWING_TICKS = game.getMaxEffectiveSwingTicks();
        if (self.getRemainingCooldownTicks() == 0) {
            BACK_AFTER_SHOOT = Vars.BACK_AFTER_SHOOT_DEFAULT;
        }
    }

    private boolean shootOrNotToShoot(double a) {
        Point net = getNet(true);
        Unit selfCopy = Moving.cast(self, net, 0.0d, 0);
        List<Unit> oppsAhead = getUnitsAhead(self, getOpps(self));

        double ticks = 0;
        while (abs(selfCopy.getAngleTo(net)) > Vars.SHOOT_ANGLE / 2 && ticks < 100) {
            selfCopy = Moving.cast(selfCopy, net, a, 1);
            ticks++;
        }
        if (ticks == 100) {
            return false;
        }

        double puckSpeed = getStrikePuckSpeed(Moving.getSpeed(selfCopy), selfCopy.getAngle(), Math.atan2(selfCopy.getSpeedY(), selfCopy.getSpeedX()));
        Unit shootingPuck = copy(world.getPuck(),
                                 selfCopy.getX() + Math.cos(selfCopy.getAngle()) * game.getPuckBindingRange(),
                                 selfCopy.getY() + Math.sin(selfCopy.getAngle()) * game.getPuckBindingRange(),
                                 puckSpeed * Math.cos(selfCopy.getAngle()), puckSpeed * Math.sin(selfCopy.getAngle()), world.getPuck().getAngle(), world.getPuck().getAngularSpeed());

        double finalX = -1.0d + Coords.getX(Coords.getWidth() / 2 + Coords.X_MIN + 30);
        Unit goalie = getGoalie();

        if (goalie != null) {
            double goalieY = shootingPuck.getY();
            if (goalieY < game.getGoalNetTop() + 30) {
                goalieY = game.getGoalNetTop() + 30;
            } else if (goalieY > game.getGoalNetTop() + game.getGoalNetHeight() - 30) {
                goalieY = game.getGoalNetTop() + game.getGoalNetHeight() - 30;
            }
            goalie = copy(goalie,
                          Coords.getXcoord(Coords.getX(goalie.getX()) + Coords.getX(Coords.getWidth() / 2 + Coords.X_MIN + 30)),
                          goalieY,
                          goalie.getSpeedX(), goalie.getSpeedY(), goalie.getAngle(), goalie.getAngularSpeed());
        }
        while (Coords.getX(shootingPuck.getX()) > finalX) {
            for (Unit opp : oppsAhead) {
                if (Moving.atStickSector(opp, shootingPuck) && Moving.getSpeed(puck) < Vars.CATCH_PUCK_SPEED) {
                    return false;
                }
            }
            if (goalie != null) {
                if (shootingPuck.getDistanceTo(goalie) < Vars.PUCK_TO_GOALIE_DISTANCE) {
                    return false;
                }
                double goalieY = goalie.getY() + game.getGoalieMaxSpeed() * (shootingPuck.getY() > goalie.getY() ? 1d : -1.0d);
                if (goalieY < game.getGoalNetTop() + 30) {
                    goalieY = game.getGoalNetTop() + 30;
                } else if (goalieY > game.getGoalNetTop() + game.getGoalNetHeight() - 30) {
                    goalieY = game.getGoalNetTop() + game.getGoalNetHeight() - 30;
                }
                goalie = copy(goalie,
                              goalie.getX(),
                              goalieY,
                              goalie.getSpeedX(), goalie.getSpeedY(), goalie.getAngle(), goalie.getAngularSpeed());
            }
            shootingPuck = Moving.cast(shootingPuck, null, 0.0d, 1);
            if (Moving.getSpeed(shootingPuck) < 0.1d) {
                return false;
            }
        }
        return true;
    }

    private boolean passToNetOrNot(double a) {
        Point net = getNet(true);
        Unit selfCopy = Moving.cast(self, net, 0.0d, 0);
        List<Unit> oppsAhead = getUnitsAhead(self, getOpps(self));

        double ticks = 0;
        while (abs(selfCopy.getAngleTo(net)) > game.getPassSector() / 2 && ticks < 100) {
            selfCopy = Moving.cast(selfCopy, net, a, 1);
            ticks++;
        }

        if (ticks == 100) {
            return false;
        }
        double puckSpeed = getPassPuckSpeed(Moving.getSpeed(selfCopy), selfCopy.getAngle(), selfCopy.getAngleTo(net), selfCopy.getAngle() + selfCopy.getAngleTo(net));
        Unit shootingPuck = copy(world.getPuck(),
                                 selfCopy.getX() + Math.cos(selfCopy.getAngle()) * game.getPuckBindingRange(),
                                 selfCopy.getY() + Math.sin(selfCopy.getAngle()) * game.getPuckBindingRange(),
                                 puckSpeed * Math.cos(selfCopy.getAngle() + selfCopy.getAngleTo(net)),
                                 puckSpeed * Math.sin(selfCopy.getAngle() + selfCopy.getAngleTo(net)),
                                 world.getPuck().getAngle(), world.getPuck().getAngularSpeed());

        double finalX = -1.0d + Coords.getX(Coords.getWidth() / 2 + Coords.X_MIN + 30);
        Unit goalie = getGoalie();

        if (goalie != null) {
            double goalieY = shootingPuck.getY();
            if (goalieY < game.getGoalNetTop() + 30) {
                goalieY = game.getGoalNetTop() + 30;
            } else if (goalieY > game.getGoalNetTop() + game.getGoalNetHeight() - 30) {
                goalieY = game.getGoalNetTop() + game.getGoalNetHeight() - 30;
            }
            goalie = copy(goalie,
                          Coords.getXcoord(Coords.getX(goalie.getX()) + Coords.getX(Coords.getWidth() / 2 + Coords.X_MIN + 30)),
                          goalieY,
                          goalie.getSpeedX(), goalie.getSpeedY(), goalie.getAngle(), goalie.getAngularSpeed());
        }
        while (Coords.getX(shootingPuck.getX()) > finalX) {
            for (Unit opp : oppsAhead) {
                if (Moving.atStickSector(opp, shootingPuck) && Moving.getSpeed(puck) < Vars.CATCH_PUCK_SPEED) {
                    return false;
                }
            }
            if (goalie != null) {
                if (shootingPuck.getDistanceTo(goalie) < Vars.PUCK_TO_GOALIE_DISTANCE) {
                    return false;
                }
                double goalieY = goalie.getY() + game.getGoalieMaxSpeed() * (shootingPuck.getY() > goalie.getY() ? 1d : -1.0d);
                if (goalieY < game.getGoalNetTop() + 30) {
                    goalieY = game.getGoalNetTop() + 30;
                } else if (goalieY > game.getGoalNetTop() + game.getGoalNetHeight() - 30) {
                    goalieY = game.getGoalNetTop() + game.getGoalNetHeight() - 30;
                }
                goalie = copy(goalie,
                              goalie.getX(),
                              goalieY,
                              goalie.getSpeedX(), goalie.getSpeedY(), goalie.getAngle(), goalie.getAngularSpeed());
            }
            shootingPuck = Moving.cast(shootingPuck, null, 0.0d, 1);
            if (Moving.getSpeed(shootingPuck) < 0.1d) {
                return false;
            }
        }
        return true;
    }

    private boolean swingOrNotToSwing() {
        if (abs(self.getAngleTo(getNet(true))) < Vars.SHOOT_ANGLE) {
            double speedAngle = Math.atan2(self.getSpeedY(), self.getSpeedX());
            double selfAngle = self.getAngle();
            if (abs(selfAngle - speedAngle) < Vars.SWING_SHIFT_BY_SPEED) {
                return true;
            }
        }
        return false;
    }
}
